neiam
/bismuth
MOTHER-FUCKING-BISMUTH
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
Log in to mark this repository.
| .forgejo | |||
| assets | |||
| examples | |||
| fonts | |||
| migration | |||
| src | |||
| templates | |||
| tests | |||
| .dockerignore | 141 B | ||
| .gitignore | 29 B | ||
| build.rs | 290 B | ||
| Cargo.toml | 2.0 KiB | ||
| compose.yml | 1021 B | ||
| Dockerfile | 2.9 KiB | ||
| Justfile | 2.0 KiB | ||
| LICENSE.md | 33.5 KiB | ||
| README.md | 7.0 KiB |
bismuth
Code screenshots with Linux window chrome, in the spirit of carbon.now.sh. Paste code, pick a KDE-era window decoration and a color scheme, then download a PNG or SVG. Or save it and embed the live card in a presentato deck, where it follows the deck’s color scheme the way Diogramos embeds do.
Built on the neiam-htmx
cargo-generate template: axum + htmx + minijinja, sea-orm on Postgres, and
invite-only session auth.
The card
| Choice | Options |
|---|---|
| Window decoration | KDE 1 (kwm), KDE 2, KDE 3 Keramik, KDE 3.5 Plastik, KDE 4 Oxygen, Plasma Breeze, none |
| Color scheme | Auto (the decoration’s own era), the KDE classics (KDE 1, KDE 2, Keramik, Plastik, Oxygen, Obsidian Coast, Breeze, Breeze Light, Breeze Dark), and the neiam schemes (her, afterdark, forest, sky, clays, stones, light, dark, lofi, black, blueprint) |
| Highlighting | Solarized by default, following the scheme: dark or light to match it. On a neiam scheme the code area also takes that scheme’s own base-100 fill and text color, keeping Solarized’s accents. Fixed themes (Solarized dark/light, Base16 Ocean/Eighties/Mocha, InspiredGitHub) keep their own background. |
| Extras | line numbers, transparent backdrop, padding |
The decorations are vector redraws that follow each original’s layout:
button placement, caption alignment, bevels, Keramik’s raised caption tab,
KDE 2’s grab bar. They aren’t copies of KDE pixmaps. The Breeze color values
come straight from Plasma’s .colors files. The KDE 1–4 sets are
reconstructions of each era’s defaults.
Everything renders through one path (src/render/): syntect highlights,
the card is composed as SVG, and the PNG export rasterises that same SVG with
resvg. The house B612 fonts are compiled in (fonts/, OFL-1.1), so exports
look the same on any host.
Editing
The editor (/) shows the rendered window and lets you type straight into
it. A transparent, caret-only textarea sits exactly over the code grid, and
each keystroke re-renders the card underneath. The plain text box is hidden
behind Show plain text box for long pastes; the two stay in sync.
Sharing and embedding
Saving (signed in) gives an unlisted random slug. Anyone with the link can:
| URL | What |
|---|---|
/s/<slug> |
share page with copy-paste snippets |
/s/<slug>/card.png |
PNG (2x by default) |
/s/<slug>/card.svg |
standalone SVG, fonts embedded |
/e/<slug> |
iframe embed, frameable anywhere (frame-ancestors *) |
All four accept look overrides, so one snippet can be dressed per placement:
?theme=<palette>, ?chrome=, ?hl=, ?bg=transparent|backdrop, ?pad=,
?ln=true|false, plus ?scale= (PNG) and ?download=1.
Presentato
The embed uses the same theme handshake as Diogramos, under its own prefix:
- the embed posts
{type: "bismuth:ready"}to its parent once it’s listening - the parent posts
{type: "bismuth:set-theme", theme}, and the card is re-rendered in that palette and swapped in
Every presentato scheme name is a bismuth palette. presentato-ma and
presentato-gmp-io speak both prefixes. In a deck:
<iframe src="https://bismuth.example/e/<slug>?bg=transparent"
style="width:100%;height:540px;border:0;color-scheme:normal" loading="lazy"></iframe>
color-scheme:normal matters: the decks declare color-scheme: dark, and
browsers paint an opaque backdrop behind an iframe whose scheme differs from
its parent’s.
Stack
| Concern | Choice |
|---|---|
| HTTP | axum 0.8 |
| Hypermedia | htmx (hx-boost, polling), axum-htmx |
| Templating | minijinja (embedded at build time) |
| Database | Postgres via sea-orm (UUIDv7 keys) |
| Migrations | sea-orm-migration (auto-run on boot) |
| Auth | Argon2 password hashing, cookie sessions, invite tokens |
| Styling | Tailwind CSS + DaisyUI + FontAwesome |
| Metrics | Prometheus at /metrics |
Binaries
webservice— the HTTP server.registration-token— CLI to mint/list invite tokens (bootstrap the first user).
Quick start (local dev)
Prereqs: Rust, just, Node.js (for the asset
build), and Docker/Podman (for Postgres).
# 1. Build front-end assets (CSS/JS/webfonts)
just deps
just assets
# 2. Start Postgres
just infra
# 3. Run the web server (auto-runs migrations on boot)
just serve # -> http://localhost:3999
# 4. Mint an invite so you can register, then open the printed URL
just invite
Registration is invite-only by default. Set REGISTRATION_ENABLED=true to let
anyone with a valid invite token self-serve, or keep it off and hand out links
from the in-app Invites page.
Configuration
All via environment variables (defaults suit local compose.yml):
| Variable | Default | Purpose |
|---|---|---|
DATABASE_URL |
postgres://app_user:app_password@localhost:5433/app |
Postgres DSN |
BIND_ADDRESS |
0.0.0.0:3999 |
Listen address |
AUTO_MIGRATE |
true |
Run migrations on webservice boot |
REGISTRATION_ENABLED |
false |
Allow public self-registration |
SESSION_DURATION_HOURS |
24 |
Session lifetime |
REGISTRATION_TOKEN_DURATION_DAYS |
7 |
Default invite lifetime |
PUBLIC_URL |
http://localhost:3999 |
Origin used in the copy-paste embed/image snippets |
Containers
just up # build + run postgres, webservice
just logs # tail the webservice
just invite-in-pod
just down
Layout
src/
bin/ webservice, registration-token
config.rs env-driven configuration
lib.rs AppState + minijinja environment
entities/ sea-orm models (users, sessions, invites, snippets)
auth/ password hashing, sessions, invite tokens, user ops
middleware/ cookie-session auth layers
handlers/ axum handlers (auth, editor/export, snippets/embeds)
render/ highlight → SVG card → PNG; chrome.rs (decorations),
palette.rs (color schemes), highlight.rs (syntect)
fonts/ B612 TTFs for rasterising (OFL-1.1)
examples/ contact_sheet: every decoration and palette to PNG/SVG
migration/ sea-orm migrations
templates/ minijinja templates (excluded from generation templating)
assets/ Tailwind/esbuild sources + vendored fonts
tests/ template render smoke tests
Regenerating entities after a schema change
sea-orm-cli generate entity -u "$DATABASE_URL" -o src/entities